package com.friendsoft.crm.bean;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.security.Principal;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import com.friendsoft.crm.entity.BaseEntity;

/**
 *
 * @author huyduong
 */
@Stateless
public class BaseEntityBean<T extends BaseEntity> {
	@PersistenceContext(unitName="fserp_pu")
	private EntityManager em ;

	@Resource
	private SessionContext sessionContext;

	private Class<T> persistentClass;

	/**
	 * @return the em
	 */
	public EntityManager getEm() {
		return em;
	}

	/**
	 * @param em the em to set
	 */
	public void setEm(EntityManager em) {
		this.em = em;
	}

	/**
	 * @return the sessionContext
	 */
	public SessionContext getSessionContext() {
		return sessionContext;
	}

	/**
	 * @param sessionContext the sessionContext to set
	 */
	public void setSessionContext(SessionContext sessionContext) {
		this.sessionContext = sessionContext;
	}

	/**
     * Gets persistent class.
     *
     * @return
     */
    @SuppressWarnings("unchecked")
	protected Class<T> getPersistentClass() {
        if (persistentClass == null) {
            persistentClass = (Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
        }
        return persistentClass;
    }

	/**
	 * Remove persistent object extends {@link BaseEntity}.
	 * After that call <code>flush()</code> to flush data to db.
	 * @param entity
	 */
	public void remove(T entity) {
		getEm().remove(entity);
		getEm().flush();
	}

	/**
	 * remove all from database
	 */
	public void removeAllData() {
		Class<T> entityClass = getPersistentClass();
		String strQuery = " DELETE FROM " + entityClass.getSimpleName() ;
		Query query = getEm().createQuery(strQuery);
		query.executeUpdate();
	}

	/**
	 * remove all from database with specific classes
	 */
	public void removeAllData(Class<?>[] classArr) {
		for(Class<?> entityClass : classArr) {
			String strQuery = " DELETE FROM " + entityClass.getSimpleName() ;
			Query query = getEm().createQuery(strQuery);
			query.executeUpdate();
		}
	}

	/**
	 * Set value to audit trail columns after that persist an entity type of {@link BaseEntity}.
	 * After that call <code>flush()</code> to flush data to db.
	 * @param entity
	 */
	public void persist(T entity) {
		Principal cp = sessionContext.getCallerPrincipal();
		if (StringUtils.isBlank(entity.getCreatedBy())) {
			entity.setCreatedBy(cp.getName());
		}

		if(entity.getCreatedDate()==null){
			entity.setCreatedDate(new Date());
		}
		getEm().persist(entity);
		getEm().flush();
	}

	/**
	 * Set value to audit trail columns after that merge an entity type of {@link BaseNormalDao}.
	 * After that call <code>flush()</code> to flush data to db.
	 * @param entity
	 */
	public void merge(T entity) {
		Principal cp = sessionContext.getCallerPrincipal();
		entity.setUpdatedBy(cp.getName());
		entity.setUpdatedDate(new Date());

		getEm().merge(entity);
		getEm().flush();
	}

	/**
	 * Find object by technical id.
	 * @param id technical id.
	 * @return object extends {@link BaseEntity}
	 */
	public T find(long id) {
		return find(id, true) ;
	}

	/**
	 * Find object by technical id with option that get fresh data from DB.
	 *
	 * @param id
	 * @param refresh
	 * @return object extends {@link BaseEntity}
	 */
	public T find(long id, boolean refresh) {
		T persistent = getEm().find(getPersistentClass(), id);
		if (persistent == null) {
			return null;
		}

		//get fresh data of entity from db
		if (refresh) {
			getEm().refresh(persistent);
		}

		return persistent;
	}

	 /**
     * Convention method supports get all data in a entity with sorted columns
     * @param orderColumns the result will be sorted by given list column
     * @return list<T> of persisted object.
     */
    @SuppressWarnings("unchecked")
	private List<T> getAllDataWithOrder(List<String> orderColumns, int firstIndex, int maxResult) {
    	Class<T> entityClass = getPersistentClass();
		String strQuery = " SELECT  e " + " FROM " + entityClass.getSimpleName() + " e ";
		if (orderColumns != null && orderColumns.size() > 0) {
			strQuery += " ORDER BY e." + StringUtils.join(orderColumns, ", e.");

		}
		Query query = getEm().createQuery(strQuery);

		//apply paging
		if (firstIndex >= 0 && maxResult > 0) {
			query.setFirstResult(firstIndex);
			query.setMaxResults(maxResult);
		}

		return query.getResultList();
    }

    /**
     * Get all data default sort by id.
     * @return List<T> persisted object.
     */
	public List<T> getAllData() {
    	return getAllDataWithOrder(null, -1, -1);
    }

	/**
	 * Get all data of an entity with paging.
	 * @param firstIndex
	 * @param maxResult
	 * @return
	 */
	public List<T> getAllData(int firstIndex, int maxResult) {
		return getAllDataWithOrder(null, firstIndex, maxResult);
	}

    /**
     * Get all data with default sort order by given column
     * @param orderColumns name of attributes in entity
     * @return list persisted object
     */
    public List<T> getAllData(List<String> orderColumns) {
    	return getAllDataWithOrder(orderColumns, -1, -1);
    }

    @SuppressWarnings("unchecked")
	public List<T> search(String str) {
    	if(StringUtils.isEmpty(str)) {
    		return getAllData();
    	}

    	Class<T> entityClass = getPersistentClass();
    	Field[] fields = entityClass.getDeclaredFields();
		String strQuery = " SELECT  e " + " FROM " + entityClass.getSimpleName() + " e WHERE ";
		if(ArrayUtils.isNotEmpty(fields)) {
			for(Field field : fields) {
				if(field.getType().equals(String.class)) {
					strQuery = strQuery + "UPPER(e." + field.getName() + ") LIKE UPPER(:keyword) OR ";
				}
			}

			strQuery = strQuery.substring(0,strQuery.lastIndexOf("OR"));
		}
		Query query = getEm().createQuery(strQuery);
		query.setParameter("keyword","%"+str+"%");
		return query.getResultList();
    }

    /**
     * Empty Cache for free memory.
     */
	public void clearCache() {
		getEm().getEntityManagerFactory().getCache().evictAll();
		getEm().clear();
	}
}
